package ir;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.tree.ErrorNode;
import org.antlr.v4.runtime.tree.TerminalNode;
import parser.*;

import java.util.*;

public class IRProduceListener extends CmmBaseListener {

    public static ArrayList<IRLine> Code;
    public static IRLine currentLine;
    public final static String COMPARE= "COMPARE";
    public final static String MULEXPR= "MULEXPR";
    public final static String EXPR= "EXPR";
    public final static String FACTOR= "FACTOR";
    public final static String STDOUT =  "STDOUT";
    public final static String STDIN =  "STDIN";
    public final static String VARIABLE = "VARIABLE";
    public final static String TYPE = "TYPE";
    public final static String FORWARD = "FORWARD";
    public final static String BACKWARD = "BACKWARD";
    public static boolean forAssignCount = true;
    public static final int
            LABEL=0, JMP = 1,MOV = 2, WRITE = 3, DECL = 4, ASSIGN = 5,
            PLUS = 6, MINUS = 7, MUL = 8, DIV = 9, MOD = 10, BIGGER_THAN = 11,
            LESS_THAN = 12, NO_LESS = 13, NO_MORE = 14, EQ = 15,
            UNEQ = 16, LOG_OR = 17, LOG_AND = 18, LOG_NOT = 19,
            Minus = 20, READ = 21, IN = 22, OUT = 23;

    public static Stack<IRLine> stack;

    public IRProduceListener(){
        Code = new ArrayList<IRLine>();
        stack = new Stack<IRLine>();
        currentLine = new IRLine();
    }

    //type deAs(COMMA deAs)* SEMI | type L_BRACKET INT R_BRACKET ID SEMI;
    @Override public void exitDeclStmt2(CmmParser.DeclStmt2Context ctx) {
        Code.add(currentLine = new IRLine(DECL, TYPE, ctx.INT().getText(), ctx.ID().getText()));
    }

    //ID | ID ASSIGN expr |ID ASSIGN MINUS expr;
    @Override public void exitDeAs1(CmmParser.DeAs1Context ctx) {
        Code.add(currentLine = new IRLine(DECL, TYPE, null, ctx.ID().getText()));
    }
    @Override public void exitDeAs2(CmmParser.DeAs2Context ctx) {
        Code.add(currentLine = new IRLine(DECL, TYPE, null, ctx.ID().getText()));
        Code.add(currentLine = new IRLine(MOV, EXPR, null, ctx.ID().getText()));
    }
    @Override public void exitDeAs3(CmmParser.DeAs3Context ctx) {
        Code.add(currentLine = new IRLine(Minus, EXPR, null, EXPR));
        Code.add(currentLine = new IRLine(DECL, TYPE, null, ctx.ID().getText()));
        Code.add(currentLine = new IRLine(MOV, EXPR, null, ctx.ID().getText()));
    }

    //ifStmt  : IF L_PAREN compare R_PAREN stmtBlock elseIfStmt*
//            | IF L_PAREN compare R_PAREN stmtBlock elseIfStmt* elseStmt;
//
    @Override public void exitIfStmt(CmmParser.IfStmtContext ctx) {
        for(int i = ctx.getChildCount()-4;i>0;i--)
            stack.pop().dest = String.valueOf(Code.size()-1);
    }

    @Override public void enterElseIfStmt(CmmParser.ElseIfStmtContext ctx) { }
    @Override public void exitElseIfStmt(CmmParser.ElseIfStmtContext ctx) { }
    // elseStmt    : ELSE stmtBlock;
    @Override public void enterElseStmt(CmmParser.ElseStmtContext ctx) {
        Code.add(currentLine = new IRLine(MOV, String.valueOf(true), null, COMPARE));
    }

    //whileStmt: WHILE L_PAREN compare R_PAREN stmtBlock;
    @Override public void enterWhileStmt(CmmParser.WhileStmtContext ctx) {
        Code.add(currentLine = new IRLine(LABEL, null,null,String.valueOf(Code.size())));
        stack.push(currentLine);
    }
    @Override public void exitWhileStmt(CmmParser.WhileStmtContext ctx) {
        Code.add(currentLine = new IRLine(JMP,null,BACKWARD,String.valueOf(stack.pop().dest)));
    }

     //    assignStmt: VARIABLE ASSIGN expr
     //          | VARIABLE ASSIGN MINUS expr
     //            ;
    @Override public void exitAssignStmt1(CmmParser.AssignStmt1Context ctx) {
        if(ctx.getParent() instanceof CmmParser.ForStmt2Context) {
            stack.push(currentLine = new IRLine(ASSIGN, EXPR, null, VARIABLE));
        } else if(ctx.getParent() instanceof CmmParser.ForStmt1Context) {
            if(forAssignCount){
                Code.add(currentLine = new IRLine(ASSIGN, EXPR, null, VARIABLE));
                forAssignCount = false;
            } else{
                stack.push(currentLine = new IRLine(ASSIGN, EXPR, null, VARIABLE));
                forAssignCount = true;
            }
        } else{
            Code.add(currentLine = new IRLine(ASSIGN, EXPR, null, VARIABLE));
        }
    }
    @Override public void exitAssignStmt2(CmmParser.AssignStmt2Context ctx) {
        if(ctx.getParent() instanceof CmmParser.ForStmt2Context) {
            stack.push(currentLine = new IRLine(ASSIGN, EXPR, null, VARIABLE));
            stack.push(currentLine = new IRLine(Minus, EXPR, null, EXPR));
        } else if(ctx.getParent() instanceof CmmParser.ForStmt1Context){
            if(forAssignCount){
                Code.add(currentLine = new IRLine(Minus, EXPR, null, EXPR));
                Code.add(currentLine = new IRLine(ASSIGN, EXPR, null, VARIABLE));
                forAssignCount = false;
            } else{
                stack.push(currentLine = new IRLine(ASSIGN, EXPR, null, VARIABLE));
                stack.push(currentLine = new IRLine(Minus, EXPR, null, EXPR));
                forAssignCount = true;
            }
        } else  {
            Code.add(currentLine = new IRLine(Minus, EXPR, null, EXPR));
            Code.add(currentLine = new IRLine(ASSIGN, EXPR, null, VARIABLE));
        }
    }

    //writeStmt: WRITE L_PAREN expr R_PAREN SEMI;
    @Override public void exitWriteStmt(CmmParser.WriteStmtContext ctx) {
        Code.add(currentLine = new IRLine(WRITE,EXPR, null, STDOUT));
    }
    @Override public void exitReadStmt(CmmParser.ReadStmtContext ctx) {
        Code.add(currentLine = new IRLine(READ, STDIN, null, VARIABLE));
    }

    @Override public void enterStmtBlock(CmmParser.StmtBlockContext ctx) {
        Code.add(currentLine = new IRLine(IN, null, null, null));
        Code.add(currentLine = new IRLine(JMP, COMPARE, FORWARD, null));
        stack.push(currentLine);
    }
    @Override public void exitStmtBlock(CmmParser.StmtBlockContext ctx) {
        if(ctx.getParent() instanceof CmmParser.WhileStmtContext || ctx.getParent() instanceof CmmParser.ForStmtContext) {
            currentLine = stack.pop();
            currentLine.dest = String.valueOf(Code.size() + 2);
        } else if (ctx.getParent() instanceof CmmParser.IfStmtContext ||ctx.getParent() instanceof CmmParser.ElseIfStmtContext) {
            Code.add(currentLine = new IRLine(JMP, null, null, null));
            stack.pop().dest = String.valueOf(Code.size());
            stack.push(Code.get(Code.size()-1));
        }
        Code.add(currentLine = new IRLine(OUT, null, null, null));
    }

    /**
     forStmt : FOR L_PAREN assignStmt SEMI compare SEMI assignStmt R_PAREN stmtBlock
    | FOR L_PAREN declStmt compare SEMI assignStmt R_PAREN stmtBlock
    ;*/
    @Override public void enterForStmt1(CmmParser.ForStmt1Context ctx) {
        Code.add(currentLine = new IRLine(LABEL, null,null,String.valueOf(Code.size())));
        stack.push(currentLine);
    }
    @Override public void exitForStmt1(CmmParser.ForStmt1Context ctx) {
        Code.add(stack.pop());
        Code.add(currentLine = new IRLine(JMP,null,BACKWARD,String.valueOf(stack.pop().dest)));
    }
    @Override public void enterForStmt2(CmmParser.ForStmt2Context ctx) {
        Code.add(currentLine = new IRLine(LABEL, null,null,String.valueOf(Code.size())));
        stack.push(currentLine);
    }
    @Override public void exitForStmt2(CmmParser.ForStmt2Context ctx) {
        Code.add(stack.pop());
        Code.add(currentLine = new IRLine(JMP,null,BACKWARD,String.valueOf(stack.pop().dest)));
    }

    //type: T_INT | T_DOUBLE | T_BOOL;
    @Override public void enterType1(CmmParser.Type1Context ctx) {
        Code.add(currentLine = new IRLine(MOV, String.valueOf(Value.INT), null, TYPE));
    }
    @Override public void exitType2(CmmParser.Type2Context ctx) {
        Code.add(currentLine = new IRLine(MOV, String.valueOf(Value.DOUBLE), null, TYPE));
    }
    @Override public void exitType3(CmmParser.Type3Context ctx) {
        Code.add(currentLine = new IRLine(MOV, String.valueOf(Value.BOOL), null, TYPE));
    }

    //variable: ID | ID L_BRACKET expr R_BRACKET ;
    @Override public void enterVariable1(CmmParser.Variable1Context ctx) {
        Code.add(currentLine = new IRLine(MOV, ctx.ID().getText(), null, VARIABLE));
    }
    @Override public void exitVariable2(CmmParser.Variable2Context ctx) {
        Code.add(currentLine = new IRLine(MOV, ctx.ID().getText(), EXPR, VARIABLE));
    }

    /**compare :  NOT compare
        | expr op=(BIGGER_THAN|LESS_THAN|NO_LESS|NO_MORE|EQUAL|UN_EQUAL) compare
        //| L_PAREN compare R_PAREN
        | expr
    ;*/
    @Override public void exitCompare1(CmmParser.Compare1Context ctx) {
        Code.add(currentLine = new IRLine(LOG_NOT, COMPARE, null, COMPARE));
    }
    @Override public void exitCompare2(CmmParser.Compare2Context ctx) {
        switch (ctx.op.getType()){
            case CmmLexer.BIGGER_THAN:
                Code.add(currentLine = new IRLine(BIGGER_THAN, EXPR, EXPR, COMPARE));
                break;
            case CmmLexer.LESS_THAN:
                Code.add(currentLine = new IRLine(LESS_THAN, EXPR, EXPR, COMPARE));
                break;
            case CmmLexer.NO_LESS:
                Code.add(currentLine = new IRLine(NO_LESS, EXPR, EXPR, COMPARE));
                break;
            case CmmLexer.NO_MORE:
                Code.add(currentLine = new IRLine(NO_MORE, EXPR, EXPR, COMPARE));
                break;
            case CmmLexer.EQUAL:
                Code.add(currentLine = new IRLine(EQ, EXPR, EXPR, COMPARE));
                break;
            case CmmLexer.UN_EQUAL:
                Code.add(currentLine = new IRLine(UNEQ, EXPR, EXPR, COMPARE));
                break;
        }
    }
    @Override public void exitCompare3(CmmParser.Compare3Context ctx) {
        Code.add(currentLine = new IRLine(MOV, EXPR, null, COMPARE));
    }

    //expr    : mulExpr op=(PLUS|MINUS) expr
    @Override public void exitExpr1(CmmParser.Expr1Context ctx) {
        switch (ctx.op.getType()) {
            case CmmLexer.PLUS:
                Code.add(currentLine = new IRLine(PLUS, EXPR, EXPR, EXPR));
                break;
            case CmmLexer.MINUS:
                Code.add(currentLine = new IRLine(MINUS, EXPR, EXPR, EXPR));
                break;
        }
    }

    //mulExpr : expr op=(MUL|DIV|MOD) expr;
    @Override public void exitMulExpr1(CmmParser.MulExpr1Context ctx) {
        switch (ctx.op.getType()) {
            case CmmLexer.MUL:
                Code.add(currentLine = new IRLine(MUL, EXPR, EXPR, EXPR));
                break;
            case CmmLexer.DIV:
                Code.add(currentLine = new IRLine(DIV, EXPR, EXPR, EXPR));
                break;
            case CmmLexer.MOD:
                Code.add(currentLine = new IRLine(MOD, EXPR, EXPR, EXPR));
                break;
        }
    }

    /**factor  : variable
              | constant
              | L_PAREN expr R_PAREN
              | L_PAREN MINUS expr R_PAREN;*/
    @Override public void exitFactor1(CmmParser.Factor1Context ctx) {
        Code.add(currentLine = new IRLine(MOV, VARIABLE, null, EXPR));
    }
    @Override public void exitFactor2(CmmParser.Factor2Context ctx) {
        Code.add(currentLine = new IRLine(MOV, ctx.constant().getText(), null, EXPR));
    }
    @Override public void exitFactor4(CmmParser.Factor4Context ctx) {
        Code.add(currentLine = new IRLine(Minus, EXPR, null, EXPR));
    }

}
